查看原文
其他

网络抓包工具tcpdump实现原理浅析

中国光大银行 中国光大银行科技创新实验室 2024-01-09

随着G行架构从集中式体系向分布式式体系转型,行内系统服务快速增多,不同的服务短期内需要在集中式体系、分布式体系之间互相调用,调用链复杂,网络通讯相关问题时有发生。系统管理员在排查网络问题时就用到一款网络抓包工具tcpdump,该工具可以将网络中传送的数据包完全截获下来进行分析,能有效的排查复杂环境的网络问题。本文将从原理的角度分析tcpdump在Linux系统的实现。



tcpdump应用介绍

tcpdump是网络数据包截获分析的运维工具。支持针对网络层、协议、主机、网络或端口的过滤,并提供and、or、not等逻辑语句帮助去除无用的信息,一般默认安装在操作系统当中,需要root权限才能使用。该工具是系统管理员和开发人员重要的运维工具,在网络和报文传输相关问题排查中立下了汗马功劳。


某应用系统生产环境接到应用报警,发现该应用系统的一台虚拟机未收到请求方发过来的报文,开发人员排查时发现,该机器没有请求相关的日志打印,无法从日志的角度判断是应用程序接收的问题还是虚拟机网络环境问题,系统管理员使用tcpdump监听该应用系统端口,迅速查找出了问题原因。


某应用系统部署在分布式体系的容器云环境,开发人员需要调用在集中式体系下的应用服务,该服务的调用需要通过多个中间服务转发,开发人员发现返回的应答报文某字段被修改了,系统管理员配合开发人员使用tcpdump抓取经过中间服务的端口和虚拟网卡上的报文,快速的找出了修改应答报文的中间服务系统。



tcpdump抓包架构

tcpdump由C语言开发,主要功能通过libpcap库实现,而libpcap是linux平台下的一个网络数据包捕获功能包, 通过内核BPF技术实现数据过滤功能。tcpdump使用BPF虚拟机的指令集定义过滤器表达式,然后传递给内核,并由解释器执行,这使得包过滤可以在内核中进行,避免了向用户态进程复制全部数据包,从而提升数据包的过滤性能。tcpdump将包过滤指令注入到内核,返回按条件过滤的数据包,提供多种输出功能将抓取的报文格式化处理能力。

tcpdump的包过滤指令由BPF代码实现,通过对libpcap库的调用可以把一个输入输出的逻辑表达式变为BPF代码,实现在用户输入的命令行和BPF代码之间的转换。tcpdump 程序支持使用 -d参数来 dump 出过滤规则转化后的BPF指令字节码,下面是一个具体的示例,这一示例用来过滤端口号为 8080 的 tcp ipv4 报文。



BPF介绍

BPF(Berkeley Packet Filter ),中文翻译为伯克利包过滤器,是类 Unix 系统上数据链路层的一种原始接口,提供一种网络数据包过滤方法。随着技术的发展,人们在BPF的基础上又提出了eBPF(extended BPF)。经过重新设计,eBPF 演进为一个通用执行引擎,在不更改内核代码的前提下,实时获取和修改操作系统的行为,可基于此开发性能分析工具、软件定义网络等诸多场景,而原来的BPF则称为cBPF(classic BPF)。


现在,Linux 内核只运行eBPF,内核会将加载的cBPF字节码透明地转换成 eBPF再执行。eBPF新的设计针对现代硬件进行了优化,eBPF 生成的指令集比旧的 BPF 解释器生成的机器码执行得更快。扩展版本也增加了虚拟机中的寄存器数量,将原有的2个32位寄存器增加到10个64位寄存器。


eBPF 程序需要挂载到某个内核路径(挂载点)才能被执行,常见的挂载点有:系统调用,内核函数进入/退出,内核跟踪点,网络数据包等等,根据挂载点功能的不同,可以分为以下四类,tcpdump挂载点即为第二类:

  1. 性能跟踪(kprobes/uprobes/tracepoints)

  2. 网络(socket/xdp)

  3. 容器(cgroup)

  4. 安全(seccomp)

tcpdump使用的包过滤指令即为cBPF,内核将提交来的cBPF字节码转化成eBPF代码加载进BPF虚拟机中,使用系统调用函数setsockopt()将BPF 程序挂载在 socket套接字上,进而过滤数据包,而BPF代码是则由内核调用BPF运行函数__bpf_prog_run()来执行。



从内核层面看tcpdump抓包流程

众所周知,应用在接收报文的时候,硬件的硬中断首先触发内核的软中断,通过内核驱动程序进入网络设备层进行数据包的处理,然后数据包进入协议栈的网络层和传输层,最后被用户进程接收。而应用在发送报文时,首先经过内核的协议层,由邻居子系统实现L3层ip地址转化为L2层mac地址,然后进入网络设备层,数据包处理完成后,经驱动程序流转,最后由硬件将报文发出。


tcpdump为了能抓取数据包,首先需要创建socket套接字,用于在应用系统接收和发送报文时获取抓取的数据包,然后将过滤条件也就是对应的BPF程序注入到内核网络设备层,获取过滤后的数据包后再进行格式化处理。



创建socket套接字

tcpdump首先通过libpcap库,调用socket()函数创建PF_PACKET套接字,该套接字提供L2层抓包分析能力,所有的底层L2包都会给到PF_PACKET 模块的回调处理函数即下文的网络收包和发包都用到的内核函数packet_rcv()函数,通过该函数将数据包写入到缓存队列,libpcap库使用系统调用函数recvfrom ()复制一份数据给tcpdump。



挂载BPF程序

tcpdump使用libpcap库的pcap_compile()函数将用户制定的过滤策略转换为BPF代码,然后使用pcap_setfilter()函数调用install_bpf_program()函数装载BPF程序,install_bpf_program()函数调用系统调用函数setsockopt(),设置SO_ATTACH_FILTER参数将BPF程序下发给内核底层,将规则注入到内核,设置过滤器,从而让规则生效。



网络收包抓取

应用接收报文时,在网络设备层,驱动程序首先调用内核函数netif_receive_skb(),通过deliver_skb()调用回调函数packet_rcv(),并使用BPF运行函数__bpf_prog_run(),来执行BPF程序过滤数据包,然后将数据包存入队列,最终复制数据包给tcpdump。而应用接收数据包则根据包的协议,选择udp或者tcp将报文送到用户进程。



网络发包抓取

应用在发送报文时,首先通过邻居子系统进入网络设备层,然后调用内核函数dev_hard_start_xmit(),该函数同样使用网络收包流程中使用的deliver_skb()函数调用回调函数packet_rcv(),并通过调用BPF运行函数__bpf_prog_run(),来执行BPF程序过滤数据包,然后将数据包存入队列,最终复制数据包给tcpdump。而应用发送数据包则通过驱动程序发送出去。



总结

tcpdump是系统管理员广泛使用的网络抓包工具,通过该工具能够轻松获取报文,并使用抓取的报文在复杂的网络环境中分析网络和报文相关问题。本文从tcpdump抓包架构入手,介绍了BPF,且从内核角度分析了内核调用BPF过滤数据包的流程,完成了tcpdump的实现原理解析,对系统管理员理解linux内核、网络连接问题排查、交易报文抓包有一定的辅助意义。


参考资料:

[1] Brendan Gregg, BPF之巅 洞悉Linux系统和应用性能,第17-19页

[2]https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/

[3] https://www.tcpdump.org/


作者 | 陈鹏礼

视觉 | 王朋玉

统筹 | 郑    洁

继续滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存